package com.greatwqs.wmall.gateway.filter;

import com.greatwqs.wmall.common.module.constants.RedisKey;
import com.greatwqs.wmall.common.module.model.HttpResponse;
import com.greatwqs.wmall.common.module.util.Base64Util;
import com.greatwqs.wmall.common.module.util.JsonUtil;
import com.greatwqs.wmall.gateway.exception.CacheValueNotFoundException;
import com.greatwqs.wmall.gateway.support.PathCheckService;

import lombok.extern.slf4j.Slf4j;

import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.time.Duration;

import static com.greatwqs.wmall.common.module.constants.HttpHeaders.*;

/**
 * 用户登录全局过滤器
 *
 * @author wang-qingsong
 * @since 2025/03/22
 */
@Slf4j
@Component
public class UserLoginGlobalFilter implements GlobalFilter, Ordered {

    @Autowired
    private PathCheckService pathCheckService;

    @Autowired
    private ReactiveStringRedisTemplate redisTemplate;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String requestPath = request.getPath().toString();
        log.info("filter, requestPath: {}", requestPath);

        // 1. 路径检查
        if (pathCheckService.shouldExclude(requestPath)) {
            log.info("no need login token, requestPath: {}", requestPath);
            return chain.filter(exchange);
        }

        // 2. Token验证流程
        String loginToken = request.getHeaders().getFirst(X_AUTH_TOKEN);
        if (loginToken == null || loginToken.isEmpty()) {
            log.info("no login token, requestPath: {}", requestPath);
            return sendUnauthorized(exchange, requestPath);
        }

        // 3. Token验证
        String redisKey = RedisKey.loginToken(loginToken);
        return Mono.just(redisKey)
                .flatMap(key -> redisTemplate.opsForValue().get(redisKey))
                // Token缓存值不存在
                .switchIfEmpty(Mono.defer(() -> {
                    log.warn("invalid redisKey: {}, cache data empty, requestPath: {}", redisKey, requestPath);
                    return Mono.error(new CacheValueNotFoundException("Not found the value, redisKey：" + redisKey));
                }))
                // 获取到值后执行续期
                .flatMap(userJson -> redisTemplate.expire(redisKey, Duration.ofHours(2)).thenReturn(userJson))
                .flatMap(userJson -> processUserData(exchange, userJson))
                .flatMap(newRequest -> chain.filter(exchange.mutate().request(newRequest).build()))
                .onErrorResume(e -> handleError(exchange, requestPath, e));
    }

    @Override
    public int getOrder() {
        return -100;
    }

    private Mono<ServerHttpRequest> processUserData(ServerWebExchange exchange, String userJson) {
        return Mono.fromCallable(() -> {
            log.debug("processUserData, userJson: {}", userJson);
            String base64User = Base64Util.encode(userJson);
            return exchange.getRequest().mutate()
                    .header(X_INTERNAL_USER, base64User)
                    .build();
        });
    }

    private Mono<Void> handleError(ServerWebExchange exchange, String requestPath, Throwable e) {
        if (e instanceof CacheValueNotFoundException) {
            log.info("handleError, CacheValueNotFoundException requestPath: {}, message: {}", requestPath, e.getMessage());
            return sendUnauthorized(exchange, requestPath);
        }

        log.error("handleError, requestPath: {}", requestPath, e);
        return sendErrorResponse(exchange, HttpStatus.INTERNAL_SERVER_ERROR, HttpResponse.internalServerError(TraceContext.traceId()));
    }

    private Mono<Void> sendUnauthorized(ServerWebExchange exchange, String requestPath) {
        log.info("sendUnauthorized, requestPath: {}", requestPath);
        return sendErrorResponse(exchange, HttpStatus.UNAUTHORIZED, HttpResponse.loginTokenNotFoundOrExpired(TraceContext.traceId()));
    }

    private Mono<Void> sendErrorResponse(ServerWebExchange exchange,
                                         HttpStatus status,
                                         HttpResponse httpResponse) {
        exchange.getResponse().setStatusCode(status);
        exchange.getResponse().getHeaders().add("Content-Type", "application/json;charset=UTF-8");

        byte[] bytes = JsonUtil.toJson(httpResponse).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);

        return exchange.getResponse().writeWith(Mono.just(buffer));
    }
}